// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.interaction {
	import actionml.Global;
	import actionml.core.LoadingUtils;
	
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;
	
	public class SliderInteractions {
		public static function initialize() {
			Global.initializeExtension(SliderInteractions, DisplayObject);
		}
		public static var instanceMethods:Object = {
			makeMediaController: function(options:Object = null) {
				if (this.isAlready("MediaController")) return;
				options ||= {};
				var target:Object = this.getValueForKeypath(options["target"]);
				var playPauseButton:DisplayObject = this.getChildByName(options["playPauseButton"] || "playPauseButton");
				var rewindButton:DisplayObject = this.getChildByName(options["rewindButton"] || "rewindButton");
				
				var lastLocation:Number = 1;
				if (!("moveToClick" in options)) {
					options.moveToClick = true;
				}
				options.minValue = target.getBeginningLocation();
				options.maxValue = target.getEndingLocation();
				options.stepSize = 2; // frames or seconds
				this.playing = false;
				this.makeSlider(options);
				this.setLocation = function(location:Number) {
					this.setSliderValue(location);
				};
				this.getLocation = function() {
					return this.getSliderValue();
				};
				this.setState = function(state:String) {
					this.playing = (state == "playing");
					if (playPauseButton) {
						if (state == "playing") {
							playPauseButton.gotoAndStop("pause");
						} else {
							playPauseButton.gotoAndStop("play");
						}
					}
				};
				this.getState = function() {
					if (this.playing) {
						return "playing";
					} else {
						return "paused";
					}
 				}
				this.updateTarget = function() {
					// this is called by change which is triggered by setSliderValue so
					// we need to break the loop by detecting if the location is 
					// actually different than the slider value
					if (!target.isA("Video")) {
						target.setLocation(Math.ceil(o.getSliderValue()));
					} else if (Math.abs(target.getLocation() - this.getSliderValue()) > .05) { // .05 is our tolerance for floating point compare
						target.setLocation(o.getSliderValue());
					}
				}
				var o:DisplayObject = this;
				this.setTarget = function(t:Object) {
					// TODO - handlers should be removed from
					// old target first if we decide to allow 
					// dynamically changing the target
					target = t;
					target.addEventListener("play", function() {
						o.setState("playing");
					});
					target.addEventListener("pause", function() {
						o.setState("paused");						
					});
					target.addEventListener("streamComplete", function() {
						o.setState("paused");						
					});
					target.addEventListener("streamInfo", function() {
						o.setRangeFromTarget();
					});
					this.setRangeFromTarget();
				};
				this.getTarget = function() {
					return target;
				};
				this.setRangeFromTarget = function() {
					this.setMinValue(target.getBeginningLocation());
					this.setMaxValue(target.getEndingLocation());	
				};
				this.addEventListener("change", function() {
					o.updateTarget();
				});
				this.addEventListener(Event.ENTER_FRAME, function() {
					if (o.sliding) {
						o.updateTarget();
					} else {
						o.setSliderValue(target.getLocation(), false);
					}
					var targetLocation:Number = target.getLocation();
					if (target.captions) {
						for each (var caption:Object in target.captions) {
							if (caption.begin < targetLocation) {
								if (caption.end && caption.end < targetLocation) {
									if (o.currentCaption) {
										o.captionText.text = "";
										o.currentCaption = null;
									}
								} else if (o.currentCaption != caption) {
									o.captionText.text = caption.text;								
									o.currentCaption = caption;
								}
								break;
							}
						}
					}
					if (!target.isA("Video")) {
						if (target.getLocation() == target.getEndingLocation() && o.playing) {
							target.stopMedia(); 
						}
					}
					lastLocation = target.getLocation();
				});
				if (playPauseButton) {
					playPauseButton.makeStateButton();
					playPauseButton.addEventListener("play", function() {
						target.playMedia();
					});
					playPauseButton.addEventListener("pause", function() {
						target.stopMedia();
					});
				}
				if (rewindButton) {
					rewindButton.makeButton();
					rewindButton.addEventListener("click", function(e:Event) {
						target.setLocation(options.minValue);
					});
				}
				this.setTarget(target);
			},
			makeScrollbar: function(options:Object = null) {
				if (this.isAlready("Scrollbar")) return;
				options ||= {};
				var inertia:int = options["inertia"] || 12;
				var targets:Array = [];
				for each (var t:String in options["targets"].split(/\s*,\s*/)) {
					targets.push(this.getValueForKeypath(t));
				}
				if (!("moveToClick" in options)) {
					options.moveToClick = true;
				}
				this.makeSlider(options);
				this.setLocation = function(location:Number) {
					this.setSliderValue(location);
				};
				this.getLocation = function() {
					return this.getSliderValue();
				};
				var maxRange:Number = this.stage[this.stageRangeProperty];
				var o:DisplayObject = this;
				var targetLocation:Number = 0;
				var currentLocation:Number = 0;
				var inMotion:Boolean = false;
				//var property:String = 
				this.addEventListener("change", function() {
					targetLocation = o.getSliderValue();
					if (!inMotion) {
						inMotion = true;
						o.addEventListener(Event.ENTER_FRAME, function() {
							var diff:Number = (targetLocation - currentLocation) / inertia;
							if (diff < 0.0001 && diff > 0) {
								diff = 0.0001;
							} else if (diff > -0.0001 && diff < 0) {
								diff = -0.0001;
							}
							currentLocation += diff;
							if (Math.abs(targetLocation - currentLocation) <= 0.0001) {
								currentLocation = targetLocation;
								inMotion = false;
								o.removeEventListener(Event.ENTER_FRAME, arguments.callee);
							}
							for each (var t:DisplayObject in targets) {
								var maxRange:Number = t.stage[o.stageRangeProperty];
								if (t.mask) {
									maxRange = t.mask[o.valueRangeProperty];
								}
								if (t[o.stageRangeProperty.replace("stage", "mask")]) {
									maxRange = t[o.stageRangeProperty.replace("stage", "mask")];
								}
								t[o.valueProperty] = (maxRange - t[o.valueRangeProperty]) * currentLocation;
							}
						});
					}
				});
			},
			makeSlider: function(options:Object = null) {
				if (this.isAlready("Slider")) return;
				var t:Object = SliderInteractions["getTracer"]({method:"makeSlider", context: "symbol"});
				options ||= {};
				var thumb:DisplayObject = this.getChildByName(options["thumb"] || "thumb");
				var track:DisplayObject = this.getChildByName(options["track"] || "track");
				var stepForwardButton:DisplayObject = this.getChildByName(options["stepForwardButton"] || "stepForwardButton");
				var stepBackwardButton:DisplayObject = this.getChildByName(options["stepBackwardButton"] || "stepBackwardButton");
				var pageForwardButton:DisplayObject = this.getChildByName(options["pageForwardButton"] || "pageForwardButton");
				var pageBackwardButton:DisplayObject = this.getChildByName(options["pageBackwardButton"] || "pageBackwardButton");
				var minValue:Number = options["minValue"] || 0;
				var maxValue:Number = options["maxValue"] || 1;
				var stepSize:Number = options["stepSize"] || .05;
				var pageSize:Number = options["pageSize"] || NaN;
				var moveToClick:Boolean = options["moveToClick"];
				if (!thumb) {
					t.error(this.name + " does not have a child named " + (options["thumb"] || "thumb") + ".  A slider must have a thumb.");
					return;
				}
				if (!track) {
					t.error(this.name + " does not have a child named " + (options["track"] || "track") + ".  A slider must have a track.");
					return;
				}
				var bounds:Rectangle = new Rectangle(track.x, track.y, track.width, track.height);
				var valueProperty:String = "x";
				var mouseValueProperty:String = "mouseX";
				var valueRangeProperty:String = "width";
				var stageRangeProperty:String = "stageWidth";
				if (track.height > track.width) {
					bounds.width = .01;
					bounds.height -= thumb.height;
					valueProperty = "y";
					mouseValueProperty = "mouseY";
					valueRangeProperty = "height";
					stageRangeProperty = "stageHeight";
				} else {
					bounds.height = .01;
					bounds.width -= thumb.width;
				}
				this.valueProperty = valueProperty;
				this.mouseValueProperty = mouseValueProperty;
				this.valueRangeProperty = valueRangeProperty;
				this.stageRangeProperty = stageRangeProperty;
				this.setSliderValue = function(v:Number, doChange:Boolean=true) {
					var newValue:Number = ((v["clampRange"](minValue, maxValue) - minValue) / (maxValue - minValue));
					if (!newValue && newValue != 0) {
						newValue = minValue;
					}
					thumb[valueProperty] = track[valueProperty] + newValue * bounds[valueRangeProperty];
					if (doChange) {
						this.doEvent("change");	
					}
				};
				this.getSliderValue = function() {
					return minValue + (maxValue - minValue) * (thumb[valueProperty] - track[valueProperty]) / bounds[valueRangeProperty];
				};
				this.setThumbPercent = function(value:Number) {
					bounds[valueRangeProperty] += thumb[valueRangeProperty];
					thumb[valueRangeProperty] = bounds[valueRangeProperty] * value;
					bounds[valueRangeProperty] -= thumb[valueRangeProperty];
				};
				this.getThumbPercent = function() {
					return thumb[valueRangeProperty] / bounds[valueRangeProperty];
				};
				this.setStepSize = function(value:Number) {
					stepSize = value;
				};
				this.getStepSize = function() {
					return stepSize;
				};
				this.setMinValue = function(value:Number) {
					minValue = value;
				};
				this.getMinValue = function() {
					return minValue;
				};
				this.setMaxValue = function(value:Number) {
					maxValue = value;
				};
				this.getMaxValue = function()	 {
					return maxValue;
				};
				this.moveForward = function() {
					thumb[valueProperty] += (stepSize / (maxValue - minValue)) * bounds[valueRangeProperty];
					if (thumb[valueProperty] > bounds[valueProperty] + bounds[valueRangeProperty]) {
						thumb[valueProperty] = bounds[valueProperty] + bounds[valueRangeProperty];
					}
					this.doEvent("change");	
				};
				this.pageForward = function() {
					if (pageSize) {
						thumb[valueProperty] += (pageSize / (maxValue - minValue)) * bounds[valueRangeProperty];
					} else {
						thumb[valueProperty] += thumb[valueRangeProperty];
					}
					if (thumb[valueProperty] > bounds[valueProperty] + bounds[valueRangeProperty]) {
						thumb[valueProperty] = bounds[valueProperty] + bounds[valueRangeProperty];
					}
					this.doEvent("change");	
				};
				this.moveBackward = function() {
					thumb[valueProperty] -= (stepSize / (maxValue - minValue)) * bounds[valueRangeProperty];
					if (thumb[valueProperty] < bounds[valueProperty]) {
						thumb[valueProperty] = bounds[valueProperty];
					}
					this.doEvent("change");	
				};
				this.pageBackward = function() {
					if (pageSize) {
						thumb[valueProperty] -= (pageSize / (maxValue - minValue)) * bounds[valueRangeProperty];
					} else {
						thumb[valueProperty] -= thumb[valueRangeProperty];	
					}
					if (thumb[valueProperty] < bounds[valueProperty]) {
						thumb[valueProperty] = bounds[valueProperty];
					}
					this.doEvent("change");	
				};
				var o:DisplayObject = this;
				this.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
					if (!track.hitTestPoint(e.stageX, e.stageY)) return;
					if (moveToClick) {
						thumb[valueProperty] = o[mouseValueProperty] - thumb[valueRangeProperty] / 2;
						if (thumb[valueProperty] < bounds[valueProperty]) {
							thumb[valueProperty] = bounds[valueProperty];
						}
						if (thumb[valueProperty] > bounds[valueProperty] + bounds[valueRangeProperty]) {
							thumb[valueProperty] = bounds[valueProperty] + bounds[valueRangeProperty];
						}
						o.doEvent("change");
						return;
					}
					if (o[mouseValueProperty] < thumb[valueProperty] && o[mouseValueProperty] > bounds[valueProperty]) {
						o.pageBackward();
					} else if (o[mouseValueProperty] > thumb[valueProperty] + thumb[valueRangeProperty] && o[mouseValueProperty] < bounds[valueProperty] + bounds[valueRangeProperty] + thumb[valueRangeProperty]) {
						o.pageForward();
					}
				});
				thumb.makeButton();
				thumb.addEventListener(MouseEvent.MOUSE_DOWN, function() {
					o.sliding = true;
					thumb.startDrag(false, bounds);
					thumb.preventMouseOut = true;
					Global.stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
					Global.stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
					function mouseMoveHandler(e:MouseEvent) {
						o.doEvent("change");
					}
					function mouseUpHandler(e:MouseEvent) {
						o.sliding = false;
						thumb.stopDrag();
						thumb.preventMouseOut = false;
						thumb.updateFocusIndicator(e);
						o.doEvent("change");
						Global.stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
						Global.stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
					}
				});
				thumb.addEventListener(KeyboardEvent.KEY_DOWN, function(e:KeyboardEvent) {
					if (e.keyCode == Keyboard.DOWN || e.keyCode == Keyboard.RIGHT) {
						o.moveForward();
					} else if (e.keyCode == Keyboard.UP || e.keyCode == Keyboard.LEFT) {
						o.moveBackward();
					} else if (e.keyCode == Keyboard.PAGE_DOWN) {
						o.pageForward();
					} else if (e.keyCode == Keyboard.PAGE_UP) {
						o.pageBackward();
					}
				});
				if (stepForwardButton) {
					stepForwardButton.makeButton();
					stepForwardButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
						o.moveForward();
					});
				}
				if (stepBackwardButton) {
					stepBackwardButton.makeButton();
					stepBackwardButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
						o.moveBackward();
					});
				}
				if (pageForwardButton) {
					pageForwardButton.makeButton();
					pageForwardButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
						o.pageForward();
					});
				}
				if (pageBackwardButton) {
					pageBackwardButton.makeButton();
					pageBackwardButton.addEventListener(MouseEvent.CLICK, function(e:MouseEvent) {
						o.pageBackward();
					});
				}
			}
		}
	}
}